# OpDiLib, an Open Multiprocessing Differentiation Library
#
# Copyright (C) 2020-2022 Chair for Scientific Computing (SciComp), TU Kaiserslautern
# Copyright (C) 2023-2025 Chair for Scientific Computing (SciComp), University of Kaiserslautern-Landau
# Homepage: https://scicomp.rptu.de
# Contact:  Prof. Nicolas R. Gauger (opdi@scicomp.uni-kl.de)
#
# Lead developer: Johannes Blühdorn (SciComp, University of Kaiserslautern-Landau)
#
# This file is part of OpDiLib (https://scicomp.rptu.de/software/opdi).
#
# OpDiLib is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# OpDiLib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along with OpDiLib. If not, see
# <http://www.gnu.org/licenses/>.
#

# directories
CODI_DIR ?= # must be set in environment
OPDI_DIR ?= # must be set in environment
TEST_DIR ?= tests
DRIVER_DIR = drivers
BUILD_DIR = build
RESULT_DIR = results

# mode possibly set in environment, RUN or REF
MODE ?= RUN

# backend, either MACRO or OMPT
BACKEND ?= MACRO

# debug possibly set in environment
DEBUG ?= yes
OUTPUT_INSTRUMENT ?= no

# possibly set in the environment, enables build from source after preprocessing
EXPLICIT_PREPROCESSOR ?= no

# possibly set in the environment, toggle whether nonempty output on stderr is treated as an error
STDERR_OUTPUT_IS_ERROR ?= yes

CXX ?= clang++

FLAGS = $(CXXFLAGS) -std=c++11

ifeq ($(DEBUG),no)
	FLAGS += -O3
else
  FLAGS += -O0 -ggdb -Wall -Wextra -Werror
  ifeq ($(OUTPUT_INSTRUMENT),yes)
    FLAGS += -DOPDI_OMP_LOGIC_INSTRUMENT=1 -DOUTPUT_INSTRUMENT
  endif
endif

ifeq ($(MODE),REF)
	FLAGS += -DBUILD_REFERENCE
else
  FLAGS += -DRUN_TESTS -fopenmp -DCODI_EnableOpenMP -DCODI_EnableOpDiLib
endif

ifeq ($(BACKEND),MACRO)
  FLAGS += -DOPDI_USE_MACRO_BACKEND
else
  FLAGS += -DOPDI_USE_OMPT_BACKEND
endif

# list all test files and extract test names
TEST_FILES = $(wildcard $(TEST_DIR)/Test**.hpp)
TESTS ?= $(patsubst $(TEST_DIR)/Test%.hpp,%,$(TEST_FILES))

# list all driver files and extract driver names
DRIVER_FILES = $(wildcard $(DRIVER_DIR)/Driver**.hpp)
DRIVERS ?= $(patsubst $(DRIVER_DIR)/Driver%.hpp,%,$(DRIVER_FILES))

# without surrounding parallel directives, privatized variables are not recognized as shared and sections are considered orphaned; hence, they need to be filtered out
getDriverTests = $(if $(filter FirstOrderReverseNoParallel, $1), $(filter-out ParallelSections ForReduction ForReductionNowait ForReductionMultiple ForFirstprivate ForLastprivate SectionsReduction SectionsReductionMultiple SectionsFirstprivate SectionsLastprivate ReductionNested SingleFirstprivate, $(TESTS)), $(TESTS))

DRIVER_TESTS =

# export directories for usage in shell scripts
export DRIVER_DIR
export TEST_DIR
export BUILD_DIR
export RESULT_DIR

# export preprocessor flag
export EXPLICIT_PREPROCESSOR

# export mode
export MODE

# default target
all:


prepareGenerate:
	@ mkdir -p build; \
	rm -r -f tempmakefile; \
	printf "# auto generated by OpDiLib\n" >> tempmakefile;

# process each driver with its associated tests
$(DRIVERS): DRIVER_TESTS = $(call getDriverTests, $@)
$(DRIVERS):
	@ echo $@": "${DRIVER_TESTS}; \
	BUILD_CASES=; \
	LINK_CASES=; \
	driver=$@; \
	for test in ${DRIVER_TESTS}; \
	do \
		bash generate.sh $$driver $$test; \
		BUILD_CASES=$$BUILD_CASES" "$$driver$$test".o"; \
		LINK_CASES=$$LINK_CASES" "$$driver$$test; \
		printf "$$driver$$test.o:\n" >> tempmakefile; \
		if [ $(EXPLICIT_PREPROCESSOR) = "no" ] ; then \
			printf "\t$(CXX) $(BUILD_DIR)/$$driver$$test.cpp -c -o $(BUILD_DIR)/$$driver$$test.o $(FLAGS) -I $(CODI_DIR) -I $(OPDI_DIR)\n" >> tempmakefile; \
		else \
			printf "\t$(CXX) -E $(BUILD_DIR)/$$driver$$test.cpp -o $(BUILD_DIR)/$$driver$$test.temp $(FLAGS) -I $(CODI_DIR) -I $(OPDI_DIR)\n" >> tempmakefile; \
			printf "\tcat $(BUILD_DIR)/$$driver$$test.temp | grep -v \"# \" > $(BUILD_DIR)/$$driver$$(test)Pre.cpp\n" >> tempmakefile; \
			printf "\t"$(CXX)" "$(BUILD_DIR)"/"$$driver$$test"Pre.cpp -c -o "$(BUILD_DIR)"/"$$driver$$test"Pre.o "$(FLAGS)" -I "$(CODI_DIR)" -I "$(OPDI_DIR)"\n" >> tempmakefile; \
		fi; \
		printf "$$driver$$test: $$driver$$test.o\n" >> tempmakefile; \
		if [ $(EXPLICIT_PREPROCESSOR) = "no" ] ; then \
			printf "\t$(CXX) $(BUILD_DIR)/$$driver$$test.o -o $(BUILD_DIR)/$$driver$$test $(FLAGS) $(LDFLAGS)\n" >> tempmakefile; \
		else \
			printf "\t$(CXX) $(BUILD_DIR)/$$driver$$(test)Pre.o -o $(BUILD_DIR)/$$driver$$(test)Pre $(FLAGS) $(LDFLAGS)\n" >> tempmakefile; \
		fi \
	done; \
	printf "build_all:$$BUILD_CASES\n" >> tempmakefile; \
	printf "link_all:$$LINK_CASES\n" >> tempmakefile;

# generate a new makefile that contains compile and link commands for all admissible combinations of drivers and tests
.PHONY: generate
generate: clean prepareGenerate $(DRIVERS)
	
# build all object files
.PHONY: compile
compile:
	$(MAKE) -f tempmakefile build_all -j12
	
# link
.PHONY: link
link:
	$(MAKE) -f tempmakefile link_all -j12
	
$(patsubst %,run%,$(DRIVERS)): DRIVER_TESTS = $(call getDriverTests, $(subst run,,$@))
$(patsubst %,run%,$(DRIVERS)): DRIVER = $(subst run,,$@)
$(patsubst %,run%,$(DRIVERS)):
	@rm -f testresults; \
	for test in ${DRIVER_TESTS}; \
	do \
		bash run.sh ${DRIVER} $$test $(MODE) $(EXPLICIT_PREPROCESSOR) $(STDERR_OUTPUT_IS_ERROR); \
	done; \
	if grep -q 1 testresults; then \
		exit 1; \
	fi;

# execute all tests
.PHONY: run
run: $(patsubst %,run%,$(DRIVERS))

# use this makefile to generate, build, link and run all tests
# cannot be used with -j, -j is used internally in recursive make call in target compile
# if you use -j, generate, compile, link and run are not executed in order
.PHONY: all
all: generate compile link run

# simply remove build directory
.PHONY: clean
clean:
	rm -r -f build
	rm -f tempmakefile
	rm -f results/*.out
	rm -f results/*.err
	rm -f testresults
